/* Copyright (c) 2008, RDFfs - Andrea Marchesini <baku@morlardf.net>
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *     * Redistributions of source code must retain the above copyright
 *       notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above copyright
 *       notice, this list of conditions and the following disclaimer in the
 *       documentation and/or other materials provided with the distribution.
 *     * Neither the name of the <organization> nor the
 *       names of its contributors may be used to endorse or promote products
 *       derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 
 */
#ifdef HAVE_CONFIG_H
#include <config.h>
#else
# error Use configure; make; make install
#endif

#include <rdffs.h>

/* /
 * --/input
 * ----/source
 * ----/error
 *
 * --/subjects
 * ----/1
 * ------/subject
 * ------/triples
 * --------/1
 * ----------/predicate
 * ----------/type
 * ----------/object <symlink to ../../../../../something>
 * --------/2
 * ----------/predicate
 * ----------/lang
 * ----------/datatype
 * ----------/type
 * ----------/object <text file>
 *
 * ----/2
 * ------/subject
 * ------/triples
 * --------/1
 * ----------/predicate
 * ----------/lang
 * ----------/datatype
 * ----------/type
 * ----------/object <text file>
 *
 * --/bnodes
 * ----/1
 * ------/triples
 * --------/1
 * ----------/predicate
 * ----------/type
 * ----------/object <symlink to ../../../../../something>
 * --------/2
 * ----------/predicate
 * ----------/lang
 * ----------/datatype
 * ----------/type
 * ----------/object <text file>
 *
 * ----/2
 * ------/triples
 * --------/1
 * ----------/predicate
 * ----------/lang
 * ----------/datatype
 * ----------/type
 * ----------/object <text file>
 */

enum rdf_path_t
{
  RDF_PATH_ERROR,

  RDF_PATH_ROOT,
  RDF_PATH_VERSION,
  RDF_PATH_INPUT_DIR,
  RDF_PATH_INPUT_SOURCE,
  RDF_PATH_INPUT_ERROR,
  RDF_PATH_SUBJECTS_ROOT,
  RDF_PATH_BNODES_ROOT,
  RDF_PATH_SUBJECT_NUMBER,
  RDF_PATH_BNODE_NUMBER,
  RDF_PATH_SUBJECT_FILE,
  RDF_PATH_TRIPLE_DIR,
  RDF_PATH_TRIPLE_NUMBER,
  RDF_PATH_TRIPLE_PREDICATE,
  RDF_PATH_TRIPLE_LANG,
  RDF_PATH_TRIPLE_DATATYPE,
  RDF_PATH_TRIPLE_TYPE,
  RDF_PATH_TRIPLE_OBJECT
};

static gint
rdf_number (gchar * string)
{
  gunichar ch;
  gchar *text = string;

  while ((ch = g_utf8_get_char (text)))
    {
      text = g_utf8_next_char (text);

      if (g_unichar_isalnum (ch) == FALSE)
	return -1;
    }

  return atoi (string);
}

static enum rdf_path_t
rdf_find_path (const char *path, gpointer * something)
{
  gboolean bnode;
  gchar **split;
  gint number;
  GList *list;

  struct rdf_t *rdf;
  struct rdf_triple_t *triple;

  /* Path: / */
  if (!strcmp (path, "/"))
    return RDF_PATH_ROOT;

  /* Path: /input */
  if (!strcmp (path, "/" RDF_INPUT_DIR))
    return RDF_PATH_INPUT_DIR;

  /* Path: /version */
  if (!strcmp (path, "/" RDF_VERSION_FILE))
    return RDF_PATH_VERSION;

  /* Path: /subjects */
  if (!strcmp (path, "/" RDF_SUBJECT_DIR))
    return RDF_PATH_SUBJECTS_ROOT;

  /* Path: /bnodes */
  if (!strcmp (path, "/" RDF_BNODE_DIR))
    return RDF_PATH_BNODES_ROOT;

  /* Split the path: */
  if (!(split = g_strsplit (path, "/", -1)))
    return RDF_PATH_ERROR;

  if (*split[0])
    {
      g_strfreev (split);
      return RDF_PATH_ERROR;
    }

  /* Path: /input/... */
  if (!strcmp (split[1], RDF_INPUT_DIR))
    {

      /* Path: /input/<something>/... */
      if (split[3])
	{
	  g_strfreev (split);
	  return RDF_PATH_ERROR;
	}

      /* Path: /input/source */
      if (!strcmp (split[2], RDF_INPUT_SOURCE_FILE))
	{
	  g_strfreev (split);
	  return RDF_PATH_INPUT_SOURCE;
	}

      /* Path: /input/error */
      if (!strcmp (split[2], RDF_INPUT_ERROR_FILE))
	{
	  g_strfreev (split);
	  return RDF_PATH_INPUT_ERROR;
	}

      g_strfreev (split);
      return RDF_PATH_ERROR;
    }

  /* Path: /subjects/... */
  if (!strcmp (split[1], RDF_SUBJECT_DIR))
    {
      bnode = FALSE;
      list = rdf_input.subjects;
    }

  /* Path: /bnodes/... */
  else if (!strcmp (split[1], RDF_BNODE_DIR))
    {
      bnode = TRUE;
      list = rdf_input.bnodes;
    }

  else
    {
      g_strfreev (split);
      return RDF_PATH_ERROR;
    }

  /* Path: /<something>/number ? */
  if ((number = rdf_number (split[2])) < 0)
    {
      g_strfreev (split);
      return RDF_PATH_ERROR;
    }

  /* The number doesn't exist */
  if (!(rdf = g_list_nth_data (list, number)))
    {
      g_strfreev (split);
      return RDF_PATH_ERROR;
    }

  /* Path: /<something>/number */
  if (!split[3])
    {
      g_strfreev (split);
      *something = rdf;
      return bnode == TRUE ? RDF_PATH_BNODE_NUMBER : RDF_PATH_SUBJECT_NUMBER;
    }

  /* Path: /<something>/number/subject */
  if (!strcmp (split[3], RDF_SUBJECT_FILE) && bnode == FALSE)
    {
      if (!split[4])
	{
	  g_strfreev (split);
	  *something = rdf;
	  return RDF_PATH_SUBJECT_FILE;
	}

      g_strfreev (split);
      return RDF_PATH_ERROR;
    }

  /* Path: /<something>/number/triples */
  if (!strcmp (split[3], RDF_TRIPLES_DIR))
    {
      if (!split[4])
	{
	  g_strfreev (split);
	  *something = rdf;
	  return RDF_PATH_TRIPLE_DIR;
	}

      /* Path: /<something>/number/triples/number ? */
      if ((number = rdf_number (split[4])) < 0)
	{
	  g_strfreev (split);
	  return RDF_PATH_ERROR;
	}

      /* This number exists... */
      if (!(triple = g_list_nth_data (rdf->triples, number)))
	{
	  g_strfreev (split);
	  return RDF_PATH_ERROR;
	}

      /* Path: /<something>/number/triples/number */
      if (!split[5])
	{
	  g_strfreev (split);
	  *something = triple;
	  return RDF_PATH_TRIPLE_NUMBER;
	}

      /* Path: /<something>/number/triples/number/predicate */
      if (!strcmp (split[5], RDF_PREDICATE_FILE))
	{
	  if (!split[6])
	    {
	      g_strfreev (split);
	      *something = triple;
	      return RDF_PATH_TRIPLE_PREDICATE;
	    }

	  g_strfreev (split);
	  return -ENOENT;
	}

      /* Path: /<something>/number/triples/number/lang */
      if (!strcmp (split[5], RDF_LANG_FILE) && triple->lang)
	{
	  if (!split[6])
	    {
	      g_strfreev (split);
	      *something = triple;
	      return RDF_PATH_TRIPLE_LANG;
	    }

	  g_strfreev (split);
	  return -ENOENT;
	}

      /* Path: /<something>/number/triples/number/datatype */
      if (!strcmp (split[5], RDF_DATATYPE_FILE) && triple->datatype)
	{
	  if (!split[6])
	    {
	      g_strfreev (split);
	      *something = triple;
	      return RDF_PATH_TRIPLE_DATATYPE;
	    }

	  g_strfreev (split);
	  return -ENOENT;
	}

      /* Path: /<something>/number/triples/number/type */
      if (!strcmp (split[5], RDF_TYPE_FILE))
	{
	  if (!split[6])
	    {
	      g_strfreev (split);
	      *something = triple;
	      return RDF_PATH_TRIPLE_TYPE;
	    }

	  g_strfreev (split);
	  return -ENOENT;
	}

      /* Path: /<something>/number/triples/number/object */
      if (!strcmp (split[5], RDF_OBJECT_FILE))
	{
	  if (!split[6])
	    {
	      g_strfreev (split);
	      *something = triple;
	      return RDF_PATH_TRIPLE_OBJECT;
	    }

	  g_strfreev (split);
	  return -ENOENT;
	}
    }

  g_strfreev (split);
  return -ENOENT;
}

static int
rdf_getattr (const char *path, struct stat *stbuf)
{
  gint i;
  GList *list;
  gpointer pointer;
  struct rdf_t *rdf;
  struct rdf_triple_t *triple;

  memset (stbuf, 0, sizeof (struct stat));

  switch (rdf_find_path (path, &pointer))
    {
    case RDF_PATH_ERROR:
      return -ENOENT;

    case RDF_PATH_ROOT:
      stbuf->st_mode = S_IFDIR | 0555;
      stbuf->st_nlink =
	2 + 1 /* /subjects */  + 1 /* /bnodes */  + 1 /* input */  +
	1 /* version */ ;
      return 0;

    case RDF_PATH_INPUT_DIR:
      stbuf->st_mode = S_IFDIR | 0555;
      stbuf->st_nlink = 2 + 1 /* /source */  + 1 /* error */ ;
      return 0;

    case RDF_PATH_INPUT_SOURCE:
      stbuf->st_mode = S_IFREG | 0644;
      stbuf->st_nlink = 1;
      stbuf->st_size = rdf_input.input_size;
      return 0;

    case RDF_PATH_INPUT_ERROR:
      stbuf->st_mode = S_IFREG | 0444;
      stbuf->st_nlink = 1;
      stbuf->st_size = rdf_input.error_size;
      return 0;

    case RDF_PATH_VERSION:
      stbuf->st_mode = S_IFREG | 0444;
      stbuf->st_nlink = 1;
      stbuf->st_size = sizeof (RDF_VERSION_STRING) - 1;
      return 0;

    case RDF_PATH_SUBJECTS_ROOT:
      stbuf->st_mode = S_IFDIR | 0555;
      stbuf->st_nlink = rdf_input.subjects_size + 2;
      return 0;

    case RDF_PATH_BNODES_ROOT:
      stbuf->st_mode = S_IFDIR | 0555;
      stbuf->st_nlink = rdf_input.bnodes_size + 2;
      return 0;

    case RDF_PATH_SUBJECT_NUMBER:
      stbuf->st_mode = S_IFDIR | 0555;
      stbuf->st_nlink = 2 + 1 /* /subject */  + 1 /* /triples */ ;
      return 0;

    case RDF_PATH_BNODE_NUMBER:
      stbuf->st_mode = S_IFDIR | 0555;
      stbuf->st_nlink = 2 + 1 /* /triples */ ;
      return 0;

    case RDF_PATH_SUBJECT_FILE:
      rdf = pointer;

      stbuf->st_mode = S_IFREG | 0444;
      stbuf->st_nlink = 1;
      stbuf->st_size = rdf->subject_size;
      return 0;

    case RDF_PATH_TRIPLE_DIR:
      rdf = pointer;

      stbuf->st_mode = S_IFDIR | 0555;
      stbuf->st_nlink = 2 + g_list_length (rdf->triples);
      return 0;

    case RDF_PATH_TRIPLE_NUMBER:
      triple = pointer;

      stbuf->st_mode = S_IFDIR | 0555;
      stbuf->st_nlink =
	2 + 1 /* /predicate */  + 1 /* /object */  + 1 /* /type */ ;

      if (triple->lang)
	stbuf->st_nlink++;

      if (triple->datatype)
	stbuf->st_nlink++;

      return 0;

    case RDF_PATH_TRIPLE_PREDICATE:
      triple = pointer;

      stbuf->st_mode = S_IFREG | 0444;
      stbuf->st_nlink = 1;
      stbuf->st_size = triple->predicate_size;
      return 0;

    case RDF_PATH_TRIPLE_LANG:
      triple = pointer;

      stbuf->st_mode = S_IFREG | 0444;
      stbuf->st_nlink = 1;
      stbuf->st_size = triple->lang_size;
      return 0;

    case RDF_PATH_TRIPLE_DATATYPE:
      triple = pointer;

      stbuf->st_mode = S_IFREG | 0444;
      stbuf->st_nlink = 1;
      stbuf->st_size = triple->datatype_size;
      return 0;

    case RDF_PATH_TRIPLE_TYPE:
      triple = pointer;

      stbuf->st_mode = S_IFREG | 0444;
      stbuf->st_nlink = 1;

      switch (triple->object_type)
	{
	case RDF_OBJECT_LITERAL:
	  stbuf->st_size = 7;	/* sizeof (literal) - 1 (the \0) */
	  break;

	case RDF_OBJECT_RESOURCE:
	  stbuf->st_size = 8;	/* sizeof (resource) - 1 (the \0) */
	  break;

	case RDF_OBJECT_BNODE:
	  stbuf->st_size = 5;	/* sizeof (bnode) -1 (the \0) */
	  break;
	}
      return 0;

    case RDF_PATH_TRIPLE_OBJECT:
      triple = pointer;

      switch (triple->object_type)
	{
	case RDF_OBJECT_LITERAL:
	  stbuf->st_mode = S_IFREG | 0444;
	  stbuf->st_nlink = 1;
	  stbuf->st_size = triple->object_size;
	  return 0;

	case RDF_OBJECT_BNODE:
	  for (list = rdf_input.bnodes, i = 0; list; list = list->next, i++)
	    {
	      struct rdf_t *rdf = list->data;

	      if (!g_utf8_collate (triple->object, rdf->subject))
		{
		  stbuf->st_mode = S_IFLNK | 0444;
		  stbuf->st_nlink = 1;
		  return 0;
		}
	    }

	  stbuf->st_mode = S_IFREG | 0444;
	  stbuf->st_nlink = 1;
	  stbuf->st_size = triple->object_size;
	  return 0;

	case RDF_OBJECT_RESOURCE:
	  for (list = rdf_input.subjects, i = 0; list; list = list->next, i++)
	    {
	      struct rdf_t *rdf = list->data;

	      if (!g_utf8_collate (triple->object, rdf->subject))
		{
		  stbuf->st_mode = S_IFLNK | 0444;
		  stbuf->st_nlink = 1;
		  return 0;
		}
	    }

	  stbuf->st_mode = S_IFREG | 0444;
	  stbuf->st_nlink = 1;
	  stbuf->st_size = triple->object_size;
	  return 0;
	}
    }

  return -ENOENT;
}

static int
rdf_readlink (const char *path, char *buf, size_t bufsiz)
{
  gint i;
  GList *list;
  gpointer pointer;
  struct rdf_triple_t *triple;

  switch (rdf_find_path (path, &pointer))
    {
    case RDF_PATH_ERROR:
      return -ENOENT;

    case RDF_PATH_ROOT:
    case RDF_PATH_INPUT_DIR:
    case RDF_PATH_INPUT_SOURCE:
    case RDF_PATH_INPUT_ERROR:
    case RDF_PATH_VERSION:
    case RDF_PATH_SUBJECTS_ROOT:
    case RDF_PATH_BNODES_ROOT:
    case RDF_PATH_SUBJECT_NUMBER:
    case RDF_PATH_BNODE_NUMBER:
    case RDF_PATH_SUBJECT_FILE:
    case RDF_PATH_TRIPLE_DIR:
    case RDF_PATH_TRIPLE_NUMBER:
    case RDF_PATH_TRIPLE_PREDICATE:
    case RDF_PATH_TRIPLE_LANG:
    case RDF_PATH_TRIPLE_DATATYPE:
    case RDF_PATH_TRIPLE_TYPE:
      return -ENOLINK;

    case RDF_PATH_TRIPLE_OBJECT:
      triple = pointer;

      switch (triple->object_type)
	{
	case RDF_OBJECT_LITERAL:
	  return -ENOLINK;

	case RDF_OBJECT_BNODE:
	  for (list = rdf_input.bnodes, i = 0; list; list = list->next, i++)
	    {
	      struct rdf_t *rdf = list->data;

	      if (!g_utf8_collate (triple->object, rdf->subject))
		{
		  snprintf (buf, bufsiz, "../../../../bnodes/%d", i);
		  return 0;
		}
	    }

	  /* INTERNAL ERROR! */
	  return -ENOENT;

	case RDF_OBJECT_RESOURCE:
	  for (list = rdf_input.subjects, i = 0; list; list = list->next, i++)
	    {
	      struct rdf_t *rdf = list->data;

	      if (!g_utf8_collate (triple->object, rdf->subject))
		{
		  snprintf (buf, bufsiz, "../../../../subjects/%d", i);
		  return 0;
		}
	    }

	  /* INTERNAL ERROR! */
	  return -ENOENT;
	}
    }

  return -ENOENT;
}

static void
rdf_readdir_number (gint len, void *buf, fuse_fill_dir_t filler,
		    off_t offset, struct fuse_file_info *fi)
{
  gint i;
  gchar buffer[1024];

  filler (buf, ".", NULL, 0);
  filler (buf, "..", NULL, 0);

  for (i = 0; i < len; i++)
    {
      snprintf (buffer, sizeof (buffer), "%d", i);
      filler (buf, buffer, NULL, 0);
    }
}

static int
rdf_readdir (const char *path, void *buf, fuse_fill_dir_t filler,
	     off_t offset, struct fuse_file_info *fi)
{
  gpointer pointer;
  struct rdf_t *rdf;
  struct rdf_triple_t *triple;

  switch (rdf_find_path (path, &pointer))
    {
    case RDF_PATH_ERROR:
      return -ENOENT;

    case RDF_PATH_ROOT:
      filler (buf, ".", NULL, 0);
      filler (buf, "..", NULL, 0);
      filler (buf, RDF_SUBJECT_DIR, NULL, 0);
      filler (buf, RDF_BNODE_DIR, NULL, 0);
      filler (buf, RDF_INPUT_DIR, NULL, 0);
      filler (buf, RDF_VERSION_FILE, NULL, 0);
      return 0;

    case RDF_PATH_INPUT_DIR:
      filler (buf, ".", NULL, 0);
      filler (buf, "..", NULL, 0);
      filler (buf, RDF_INPUT_SOURCE_FILE, NULL, 0);
      filler (buf, RDF_INPUT_ERROR_FILE, NULL, 0);
      return 0;

    case RDF_PATH_INPUT_SOURCE:
    case RDF_PATH_INPUT_ERROR:
    case RDF_PATH_VERSION:
      return -ENOTDIR;

    case RDF_PATH_SUBJECTS_ROOT:
      rdf_readdir_number (rdf_input.subjects_size, buf, filler, offset, fi);
      return 0;

    case RDF_PATH_BNODES_ROOT:
      rdf_readdir_number (rdf_input.bnodes_size, buf, filler, offset, fi);
      return 0;

    case RDF_PATH_SUBJECT_NUMBER:
      filler (buf, ".", NULL, 0);
      filler (buf, "..", NULL, 0);
      filler (buf, RDF_SUBJECT_FILE, NULL, 0);
      filler (buf, RDF_TRIPLES_DIR, NULL, 0);
      return 0;

    case RDF_PATH_BNODE_NUMBER:
      filler (buf, ".", NULL, 0);
      filler (buf, "..", NULL, 0);
      filler (buf, RDF_TRIPLES_DIR, NULL, 0);
      return 0;

    case RDF_PATH_SUBJECT_FILE:
      return -ENOTDIR;

    case RDF_PATH_TRIPLE_DIR:
      rdf = pointer;
      rdf_readdir_number (g_list_length (rdf->triples), buf, filler, offset,
			  fi);
      return 0;

    case RDF_PATH_TRIPLE_NUMBER:
      triple = pointer;

      filler (buf, ".", NULL, 0);
      filler (buf, "..", NULL, 0);
      filler (buf, RDF_PREDICATE_FILE, NULL, 0);
      filler (buf, RDF_OBJECT_FILE, NULL, 0);
      filler (buf, RDF_TYPE_FILE, NULL, 0);

      if (triple->lang)
	filler (buf, RDF_LANG_FILE, NULL, 0);

      if (triple->datatype)
	filler (buf, RDF_DATATYPE_FILE, NULL, 0);

      return 0;

    case RDF_PATH_TRIPLE_PREDICATE:
    case RDF_PATH_TRIPLE_LANG:
    case RDF_PATH_TRIPLE_DATATYPE:
    case RDF_PATH_TRIPLE_TYPE:
    case RDF_PATH_TRIPLE_OBJECT:
      return -ENOTDIR;
      triple = pointer;
    }

  return -ENOENT;
}

static int
rdf_truncate (const char *path, off_t offset)
{
  gpointer pointer;

  switch (rdf_find_path (path, &pointer))
    {
    case RDF_PATH_ERROR:
      return -ENOENT;

    case RDF_PATH_ROOT:
    case RDF_PATH_INPUT_DIR:
      return -EISDIR;

    case RDF_PATH_INPUT_SOURCE:
      rdf_input.input = g_realloc (rdf_input.input, offset);

      if (offset >= rdf_input.input_size)
	memset (rdf_input.input + rdf_input.input_size, 0,
		offset - rdf_input.input_size);

      rdf_input.input_size = offset;
      return 0;

    case RDF_PATH_INPUT_ERROR:
    case RDF_PATH_VERSION:
      return -EACCES;

    case RDF_PATH_SUBJECTS_ROOT:
    case RDF_PATH_BNODES_ROOT:
    case RDF_PATH_SUBJECT_NUMBER:
    case RDF_PATH_BNODE_NUMBER:
      return -EISDIR;

    case RDF_PATH_SUBJECT_FILE:
      return -EACCES;

    case RDF_PATH_TRIPLE_DIR:
    case RDF_PATH_TRIPLE_NUMBER:
      return -EISDIR;

    case RDF_PATH_TRIPLE_PREDICATE:
    case RDF_PATH_TRIPLE_LANG:
    case RDF_PATH_TRIPLE_DATATYPE:
    case RDF_PATH_TRIPLE_TYPE:
    case RDF_PATH_TRIPLE_OBJECT:
      return -EACCES;
    }

  return -1;
}

static int
rdf_open (const char *path, struct fuse_file_info *fi)
{
  gpointer pointer;

  switch (rdf_find_path (path, &pointer))
    {
    case RDF_PATH_ERROR:
      return -ENOENT;

    case RDF_PATH_ROOT:
    case RDF_PATH_INPUT_DIR:
      return -EISDIR;

    case RDF_PATH_INPUT_SOURCE:
      /* It can be written */
      return 0;

    case RDF_PATH_INPUT_ERROR:
    case RDF_PATH_VERSION:
      if ((fi->flags & 3) != O_RDONLY)
	return -EACCES;

      return 0;

    case RDF_PATH_SUBJECTS_ROOT:
    case RDF_PATH_BNODES_ROOT:
    case RDF_PATH_SUBJECT_NUMBER:
    case RDF_PATH_BNODE_NUMBER:
      return -EISDIR;

    case RDF_PATH_SUBJECT_FILE:
      if ((fi->flags & 3) != O_RDONLY)
	return -EACCES;

      return 0;

    case RDF_PATH_TRIPLE_DIR:
    case RDF_PATH_TRIPLE_NUMBER:
      return -EISDIR;

    case RDF_PATH_TRIPLE_PREDICATE:
    case RDF_PATH_TRIPLE_LANG:
    case RDF_PATH_TRIPLE_DATATYPE:
    case RDF_PATH_TRIPLE_TYPE:
    case RDF_PATH_TRIPLE_OBJECT:
      if ((fi->flags & 3) != O_RDONLY)
	return -EACCES;

      return 0;
    }

  return -ENOENT;
}

static int
rdf_rw_real (gchar ** string, gsize * string_size, char *buf, size_t size,
	     off_t offset, struct fuse_file_info *fi, gboolean read)
{
  /* Read: */
  if (read == TRUE)
    {
      if (offset < *string_size)
	{
	  if (offset + size > *string_size)
	    size = *string_size - offset;

	  memcpy (buf, *string + offset, size);
	}
      else
	size = 0;
    }

  /* Write: */
  else
    {
      if (*string_size <= size + offset)
	{
	  gchar *tmp;
	  gsize tmp_size = offset + size;

	  if (!(tmp = g_realloc (*string, tmp_size)))
	    return -ENOMEM;

	  if (offset >= *string_size)
	    memset (tmp + *string_size, 0, tmp_size - *string_size);

	  *string = tmp;
	  *string_size = tmp_size;
	}

      memcpy (*string + offset, buf, size);
    }

  return size;
}

static int
rdf_read (const char *path, char *buf, size_t size, off_t offset,
	  struct fuse_file_info *fi)
{
  gpointer pointer;
  struct rdf_t *rdf;
  struct rdf_triple_t *triple;

  switch (rdf_find_path (path, &pointer))
    {
    case RDF_PATH_ERROR:
      return -ENOENT;

    case RDF_PATH_ROOT:
    case RDF_PATH_INPUT_DIR:
      return -EISDIR;

    case RDF_PATH_INPUT_SOURCE:
      return rdf_rw_real (&rdf_input.input, &rdf_input.input_size, buf, size,
			  offset, fi, TRUE);

    case RDF_PATH_INPUT_ERROR:
      return rdf_rw_real (&rdf_input.error, &rdf_input.error_size, buf, size,
			  offset, fi, TRUE);

    case RDF_PATH_VERSION:
      {
	gchar *tmp = g_strdup (RDF_VERSION_STRING);
	gsize tmp_size = sizeof (RDF_VERSION_STRING) - 1;	/* -1 is for \0 */
	int ret;

	ret = rdf_rw_real (&tmp, &tmp_size, buf, size, offset, fi, TRUE);
	g_free (tmp);

	return ret;
      }

    case RDF_PATH_SUBJECTS_ROOT:
    case RDF_PATH_BNODES_ROOT:
    case RDF_PATH_SUBJECT_NUMBER:
    case RDF_PATH_BNODE_NUMBER:
      return -EISDIR;

    case RDF_PATH_SUBJECT_FILE:
      rdf = pointer;
      return rdf_rw_real (&rdf->subject, &rdf->subject_size, buf, size,
			  offset, fi, TRUE);

    case RDF_PATH_TRIPLE_DIR:
    case RDF_PATH_TRIPLE_NUMBER:
      return -EISDIR;

    case RDF_PATH_TRIPLE_PREDICATE:
      triple = pointer;
      return rdf_rw_real (&triple->predicate, &triple->predicate_size, buf,
			  size, offset, fi, TRUE);

    case RDF_PATH_TRIPLE_LANG:
      triple = pointer;
      return rdf_rw_real (&triple->lang, &triple->lang_size, buf, size,
			  offset, fi, TRUE);

    case RDF_PATH_TRIPLE_DATATYPE:
      triple = pointer;
      return rdf_rw_real (&triple->datatype, &triple->datatype_size, buf,
			  size, offset, fi, TRUE);

    case RDF_PATH_TRIPLE_TYPE:
      {
	gchar *tmp;
	gsize size;
	int ret;

	triple = pointer;
	switch (triple->object_type)
	  {
	  case RDF_OBJECT_LITERAL:
	    tmp = g_strdup ("literal");
	    size = 7;

	    ret = rdf_rw_real (&tmp, &size, buf, size, offset, fi, TRUE);
	    g_free (tmp);

	    return ret;

	  case RDF_OBJECT_RESOURCE:
	    tmp = g_strdup ("resource");
	    size = 8;

	    ret = rdf_rw_real (&tmp, &size, buf, size, offset, fi, TRUE);
	    g_free (tmp);

	    return ret;

	  case RDF_OBJECT_BNODE:
	    tmp = g_strdup ("bnode");
	    size = 5;

	    ret = rdf_rw_real (&tmp, &size, buf, size, offset, fi, TRUE);
	    g_free (tmp);

	    return ret;
	  }

	return -1;
      }

    case RDF_PATH_TRIPLE_OBJECT:
      triple = pointer;
      return rdf_rw_real (&triple->object, &triple->object_size, buf, size,
			  offset, fi, TRUE);
    }

  return -1;
}

static int
rdf_write (const char *path, const char *buf, size_t size, off_t offset,
	   struct fuse_file_info *fi)
{
  gpointer pointer;

  switch (rdf_find_path (path, &pointer))
    {
    case RDF_PATH_ERROR:
      return -ENOENT;

    case RDF_PATH_ROOT:
    case RDF_PATH_INPUT_DIR:
      return -EISDIR;

    case RDF_PATH_INPUT_SOURCE:
      return rdf_rw_real (&rdf_input.input, &rdf_input.input_size,
			  (char *) buf, size, offset, fi, FALSE);

    case RDF_PATH_INPUT_ERROR:
    case RDF_PATH_VERSION:
      return -EACCES;

    case RDF_PATH_SUBJECTS_ROOT:
    case RDF_PATH_BNODES_ROOT:
    case RDF_PATH_SUBJECT_NUMBER:
    case RDF_PATH_BNODE_NUMBER:
      return -EISDIR;

    case RDF_PATH_SUBJECT_FILE:
      return -EACCES;

    case RDF_PATH_TRIPLE_DIR:
    case RDF_PATH_TRIPLE_NUMBER:
      return -EISDIR;

    case RDF_PATH_TRIPLE_PREDICATE:
    case RDF_PATH_TRIPLE_LANG:
    case RDF_PATH_TRIPLE_DATATYPE:
    case RDF_PATH_TRIPLE_TYPE:
    case RDF_PATH_TRIPLE_OBJECT:
      return -EACCES;
    }

  return -1;
}

static int
rdf_flush (const char *path, struct fuse_file_info *fi)
{
  gpointer pointer;

  switch (rdf_find_path (path, &pointer))
    {
    case RDF_PATH_ERROR:
      return -ENOENT;

    case RDF_PATH_ROOT:
    case RDF_PATH_INPUT_DIR:
      return -EISDIR;

    case RDF_PATH_INPUT_SOURCE:
      rdf_destroy_data ();
      rdf_parse (rdf_input.input, rdf_input.filename, &rdf_input.error,
		 &rdf_input.error_size);
      return 0;

    case RDF_PATH_INPUT_ERROR:
    case RDF_PATH_VERSION:
      return 0;

    case RDF_PATH_SUBJECTS_ROOT:
    case RDF_PATH_BNODES_ROOT:
    case RDF_PATH_SUBJECT_NUMBER:
    case RDF_PATH_BNODE_NUMBER:
      return -EISDIR;

    case RDF_PATH_SUBJECT_FILE:
      return 0;

    case RDF_PATH_TRIPLE_DIR:
    case RDF_PATH_TRIPLE_NUMBER:
      return -EISDIR;

    case RDF_PATH_TRIPLE_PREDICATE:
    case RDF_PATH_TRIPLE_LANG:
    case RDF_PATH_TRIPLE_DATATYPE:
    case RDF_PATH_TRIPLE_TYPE:
    case RDF_PATH_TRIPLE_OBJECT:
      return 0;
    }

  return -1;
}

void
rdf_destroy (void *dummy)
{
  rdf_destroy_data ();

  if (rdf_input.input)
    {
      g_free (rdf_input.input);
      rdf_input.input = NULL;
    }
}

void
rdf_destroy_data (void)
{
  if (rdf_input.subjects)
    {
      g_list_foreach (rdf_input.subjects, (GFunc) rdf_free, NULL);
      g_list_free (rdf_input.subjects);
      rdf_input.subjects = NULL;

      rdf_input.subjects_size = 0;
    }

  if (rdf_input.bnodes)
    {
      g_list_foreach (rdf_input.bnodes, (GFunc) rdf_free, NULL);
      g_list_free (rdf_input.bnodes);
      rdf_input.bnodes = NULL;

      rdf_input.bnodes_size = 0;
    }

  if (rdf_input.error)
    {
      g_free (rdf_input.error);
      rdf_input.error = NULL;

      rdf_input.error_size = 0;
    }
}

static int
rdf_create (const char *path, mode_t mode, struct fuse_file_info *fi)
{
  return rdf_open (path, fi);
}

static int
rdf_ftruncate (const char *path, off_t offset, struct fuse_file_info *fi)
{
  return rdf_truncate (path, offset);
}

static int
rdf_fgetattr (const char *path, struct stat *stbuf, struct fuse_file_info *fi)
{
  return rdf_getattr (path, stbuf);
}

/* FUSE DATA STRUCT ********************************************************/

struct fuse_operations rdf_oper = {
  .getattr = rdf_getattr,
  .readlink = rdf_readlink,

  .getdir = NULL,
  .mknod = NULL,
  .unlink = NULL,
  .rmdir = NULL,
  .symlink = NULL,
  .rename = NULL,
  .link = NULL,

  .chmod = NULL,
  .chown = NULL,

  .truncate = rdf_truncate,

  .utime = NULL,

  .open = rdf_open,
  .read = rdf_read,
  .write = rdf_write,

  .statfs = NULL,

  .flush = rdf_flush,
  .release = NULL,
  .fsync = NULL,

  .setxattr = NULL,
  .getxattr = NULL,
  .listxattr = NULL,
  .removexattr = NULL,

  .opendir = NULL,
  .readdir = rdf_readdir,
  .releasedir = NULL,
  .fsyncdir = NULL,

  .init = NULL,
  .destroy = rdf_destroy,

  .access = NULL,
  .create = rdf_create,
  .ftruncate = rdf_ftruncate,
  .fgetattr = rdf_fgetattr,

  .lock = NULL,
  .utimens = NULL,
  .bmap = NULL
};

/* EOF */
